﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using GeneralDFS.BusinessLogical.Nodes;
using GeneralDFS.BusinessLogical.Protocol;
using GeneralDFS.Common;
using GeneralDFS.Common.IO;
using GeneralDFS.DataEntity.Enum;
using GeneralDFS.DataEntity.Nodes;
using GeneralDFS.DataEntity.IO;


namespace GeneralDFS.Client
{
    public class DFSInputStream
    {
        private readonly String _filePath;
        private readonly Client _client;
        private readonly NameNodeInfo _nameNodeInfo;
        private LocatedBlock _currentBlock;
        private List<LocatedBlock> _locatedBlocks;
        private int _index;
        private NetworkStream _networkStream;
        //private List<Tuple<DateTime,>> dateTimes;
        /// <summary>
        /// 初始化Stream
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="client"></param>
        public DFSInputStream(string filePath, Client client)
        {
            if(!PathHelper.CheckPathVaild(filePath))
                throw new ArgumentException("invaild filepath");

            _filePath = filePath;
            _client = client ?? new Client();

            _nameNodeInfo = _client.FindNameNode(filePath);
            if (_nameNodeInfo == null)
            {
                throw new Exception("Client can not get NameNodeInfo");
            }
            var remoteObject = NameNode.GetInstance(_nameNodeInfo);
            if (!remoteObject.ExistFile(filePath))
            {
                Create();
            }
            else
            {
                Open();
            }
        }

        private void Create()
        {
            
            var remoteObject = NameNode.GetInstance(_nameNodeInfo);

            if (NameNode.IsInstanceValid(remoteObject))
            {
                //DateTime beginTime = DateTime.Now;
                remoteObject.CreateFileByPath(_filePath);
                //DateTime endTime = DateTime.Now;
                //Console.WriteLine((endTime - beginTime).TotalSeconds);
                _locatedBlocks = remoteObject.GetLocatedBlocks(_filePath);
            }

            var remoteObjectNext = NameNode.GetInstance(remoteObject.Successor);
            if (NameNode.IsInstanceValid(remoteObject))
            {
                remoteObjectNext.CreateFileByPath(_filePath);
                _locatedBlocks = remoteObjectNext.GetLocatedBlocks(_filePath);
            }
        }

        private void Open()
        {
            var remoteObject = NameNode.GetInstance(_nameNodeInfo);
            if (NameNode.IsInstanceValid(remoteObject))
            {
                _locatedBlocks = remoteObject.GetLocatedBlocks(_filePath);
            }
        }

        public void Seek(long offset,long blockLength)
        {
            _index = (int)(offset / _client.GetConfig().DetailConfig.BlockSize);
            if(_locatedBlocks==null)
                _locatedBlocks=new List<LocatedBlock>();
            if (_index >= _locatedBlocks.Count || _locatedBlocks.Count==0)
            {
                var block=new LocatedBlock
                    {
                        DataNodeIds = new DataNodeID[1],
                        FileBlockMetaData =new FileBlockMetaData
                            {
                                Guid = Guid.NewGuid(),
                                Offset = offset,
                                Length = blockLength,
                                Hash = "",
                                FullPath = _filePath,
                                FileName = PathHelper.FileName(_filePath),
                                IsSmallFile = offset==0&&blockLength<=64*1024
                            }
                    };
                var remoteObject = NameNode.GetInstance(_nameNodeInfo);
                if (NameNode.IsInstanceValid(remoteObject))
                {
                    block.DataNodeIds[0] = remoteObject.GetDataNodeID();
                }
                _locatedBlocks.Add(block);
            }
            if (offset >
                _locatedBlocks[_locatedBlocks.Count - 1].FileBlockMetaData.Offset +
                _locatedBlocks[_locatedBlocks.Count - 1].FileBlockMetaData.Length)
            {
                _locatedBlocks[_locatedBlocks.Count - 1].FileBlockMetaData.Length = offset -
                                                                                    _locatedBlocks[
                                                                                        _locatedBlocks.Count - 1]
                                                                                        .FileBlockMetaData.Offset;
            }
            try
            {
                _currentBlock = _locatedBlocks[_index];
            }
            catch (Exception)
            {
                throw new Exception(string.Format("index:{0} count:{1} offset{2}", _index, _locatedBlocks.Count,offset));
            }
            var client = new TcpClient(_currentBlock.DataNodeIds[0].Ip, _currentBlock.DataNodeIds[0].TcpPort);
            //_networkStream.Add(client.GetStream());
            _networkStream = client.GetStream();
        }

        public void WriteBlock(long offset, FileStream blockFileStream, long blockLength)
        {
            Seek(offset,blockLength);
            ProcessOp.WriteOp(new BinaryWriter(_networkStream),
                              _currentBlock.FileBlockMetaData.IsSmallFile ? Op.WriteSmallBlock : Op.WriteBlock);

            DataTranslater.WriteBlock(_currentBlock.FileBlockMetaData, new BinaryWriter(_networkStream),blockFileStream,_client.GetConfig().DetailConfig.BuffSize);
            //DataTranslater.ReadHash(new BinaryReader(_networkStream));
            _networkStream.Dispose();
        }
        //private void WriteBlock(long offset, long blockLength, MemoryMappedViewAccessor mmva,long fileLength)
        //{
        //    Seek(offset, blockLength);
        //    ProcessOp.WriteOp(new BinaryWriter(_networkStream),
        //                      _currentBlock.FileBlockMetaData.IsSmallFile ? Op.WriteSmallBlock : Op.WriteBlock);
        //    DataTranslater.WriteBlock(_currentBlock.FileBlockMetaData, new BinaryWriter(_networkStream), mmva, _client.GetConfig().DetailConfig.BuffSize);
        //    DataTranslater.ReadHash(new BinaryReader(_networkStream));
        //    _networkStream.Dispose();
        //}

        /// <summary>
        /// Write a file to the system at<paramref name="src"/>
        /// </summary>
        /// <param name="localPath">the file path in the client</param>
        public String WriteFile(String localPath)
        {
            FileInfo fileInfo = new FileInfo(localPath);
            if (fileInfo.Exists)
            {
                long blockSize = _client.GetConfig().DetailConfig.BlockSize;
                var count = fileInfo.Length%blockSize == 0
                                ? fileInfo.Length/blockSize
                                : fileInfo.Length/blockSize + 1;
                FileStream fileStream = File.Open(localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                //fileStream.Seek(0, SeekOrigin.Begin);
                for (var i = 0; i < count; i++)
                {
                    long offset = i*blockSize;
                    long blockLength = i == count - 1 ? fileInfo.Length - offset : blockSize;
                    WriteBlock(offset, fileStream, blockLength);
                }
                return _filePath;
            }
            throw new FileNotFoundException(localPath);
        }

        public void Close()
        {
            var remoteObject = NameNode.GetInstance(_nameNodeInfo);
            LocatedBlock[] located = _locatedBlocks.ToArray();
            if (NameNode.IsInstanceValid(remoteObject))
            {
                remoteObject.ReplaceBlocksByPath(_filePath, located);
            }
            var remoteObjectNext = NameNode.GetInstance(remoteObject.Successor);
            if (NameNode.IsInstanceValid(remoteObjectNext))
            {
                remoteObjectNext.ReplaceBlocksByPath(_filePath, located);
            }
        }

    }
}